#include "CommandDispatcher.h"

#include <string_view>
#include <cstring>

#include "Log.h"
#include "StringUtility.h"
#include "CommandParser.h"
#include "ConnectionStatus.h"
#include "NetworkManager.h"
#include "LoopScheduler.h"

CommandDispatcher::CommandDispatcher() :super(40*1000), pool(1000), mreceiveBuffer(MAX_RECEIVE_BUFFER_SIZE, 0), mreadIndex(0), mcommandParser(CommandParserSingleton::instance()), mnetworkManager(NetworkManagerSingleton::instance()) {
    mcommandParser.mparentCommandDispatcher = this;

    this->setWorkPool(&pool);
    mTimeoutElement = StringUtility::EMPTY_STRING;

    resetAddressInfo(ConfigManagerSingleton::instance().getConfigInfo().serverAddress);
}



CommandDispatcher::~CommandDispatcher() {
    //dtor
}


ssize_t CommandDispatcher::recv(NetworkString& buffer, const size_t beginIndex) {
    int retry = 90/DEFAULT_TIMEOUT_SECONDS;

    int ret =  0;

    do {
        ret = msocket.recv(buffer, beginIndex);
        if (0 >= ret) {
            if (EWOULDBLOCK == errno) {
                // short flags = INetwork::getIterfaceFlags(mpCurrentNetwork->mname, msocket.msocket);
                if (!mpCurrentNetwork->running()) {//INetwork::running(flags)
                    mofflineReason = OfflineReason::HARDWARE_FAILURE;
                }
            } else {
                std::string message{"recv from socket error"};
                LOG_ERROR(buildErrorMessage(ret, errno, message).data());
            }
        }
    } while ((0 > ret && EWOULDBLOCK == errno && --retry && OfflineReason::ONLINE == mofflineReason) || EINTR == errno);

//    if (0 >= ret && OfflineReason::ONLINE == mofflineReason) {
//        analysisOfflineReason();
//    }

    return ret;
}


OfflineReason CommandDispatcher::analysisOfflineReason() {
    if (mpCurrentNetwork->running()) {
        if (testNetwork()) {
            mofflineReason = OfflineReason::SERVER_FAILURE;
        } else {
            mofflineReason = OfflineReason::NETWORK_STACK_FAILURE;
        }
    } else {
        mofflineReason = OfflineReason::HARDWARE_FAILURE;
    }

    return mofflineReason;
}



bool CommandDispatcher::testNetwork() {

    bool result = mtestSocket.allocHandle();
    result = result && mtestSocket.setTimeout(*mpCurrentNetwork);
    result = result && mtestSocket.connect(ipAddressInfo, msockaddr);

    mtestSocket.close();

    return result;
}



bool CommandDispatcher::processPoolItem(super::PointType element) {
    bool result = true;
    if (ConnectionStatus::ONLINE !=  mstatus) {//already offline
        if (&mTimeoutElement != element) {//cosume product
            delete element;
            element = nullptr;
        }

        LOG_DEDUG("connection offline, system connecting...", mreceiveBuffer.data());

        if (!connect(true)) {
            close(false);
        }
    } else {
        int32_t processedBytes = 0;
        if (&mTimeoutElement == element) {
            processedBytes = recv(mreceiveBuffer, mreadIndex);
            if (0 < processedBytes) {
                mreadedBytes = mreadIndex + processedBytes;
                mreceiveBuffer[mreadedBytes] = 0;

                //    LOG_DEDUG("received server commands:[%s]", mreceiveBuffer.data());
//                printf("received server commands:[%s]\n", mreceiveBuffer.data());
                parseReceivedData();
            }

        } else {
            mlastCommandResponse = *element;

            processedBytes = sendInternal(*element);

            delete element;
            element = nullptr;
        }

        if (0 >= processedBytes) { //recv/send failed, connection offline
            close(true);
        }
    }
    return result;
}



void CommandDispatcher::send(const NetworkString& data) {
//    NetworkString* result = new NetworkString(data);
//    LOG_DEDUG("data:%s, before to network task queue", data.data());
//    pool.pushPool(result);
    if (ConnectionStatus::ONLINE == mstatus) {
        mlastCommandResponse = data;

        sendInternal(data);
    } else {
        LOG_DEDUG("connection offline, send failed[%s]", data.data());
    }

//    LOG_DEDUG("data:%s, push to network task queue", data.data());
}



void CommandDispatcher::parseReceivedData() {
    int32_t startIndex = -1;
    int32_t endIndex = -1;
    int32_t length = mreadedBytes;
    char* pData = mreceiveBuffer.data();
    for(int32_t index = 0; index < length; ++index) {
        for(; index < length; ++index) {
            if ('<' == pData[index]) {
                startIndex = index;
                break;
            }
        }

        if (0 > startIndex) {
            mreadIndex = 0;
        } else {
            for(++index; index < length; ++index) {
                if ('>' == pData[index]) {
                    endIndex = index;
                    break;
                }
            }

            if (0 > endIndex) {
                //mreceiveBuffer.erase(0, startIndex);
                mreadIndex = mreadedBytes - startIndex;
                std::memmove(&mreceiveBuffer[0], &mreceiveBuffer[startIndex], mreadIndex);
                //  mreadIndex = mreceiveBuffer.size();

            } else {
                NetworkString command(pData + startIndex, endIndex - startIndex + 1);
                parseCommandExecute(command);

                //reset
                startIndex = -1;
                endIndex = -1;

                mreadIndex = 0;
            }
        }
    }
    mreadedBytes = 0;
}



void CommandDispatcher::parseCommandExecute(NetworkString& data) {
    NetworkString tempResult;
    NetworkString* result = &tempResult; //new NetworkString();
    result->reserve(STRING_BUFFER_SIZE);
    mlastCommandResponse = data;
    mcommandParser.parseCommand(data, *result);

    mlastCommandResponse.append(1, '/').append(tempResult);

    sendInternal(tempResult);
}



bool CommandDispatcher::connect(const bool notify) {
    mpCurrentNetwork = mnetworkManager.mpactiveNetwork;

    bool result = msocket.allocHandle();
    result = result && msocket.setTimeout(*mpCurrentNetwork);
    result = result && msocket.connect(ipAddressInfo, msockaddr);

    if (result && ConnectionStatus::ONLINE != mstatus) {
        mstatus = ConnectionStatus::ONLINE;
        if (notify) {
            for (NetworkListener* pNetworkListener : mnetworkListeners){
                pNetworkListener->online(mnetworkManager.mpactiveNetwork);
                //            LoopSchedulerSingleton::instance().online(mnetworkManager.mpactiveNetwork);
            }
        }
    }

    if (result) {
        mretryCount = MAX_RECONNECT;
        mnetworkManager.mretryCount = 0;
        mofflineReason = OfflineReason::ONLINE;
    } else {
//
    }


    return result;
}



bool CommandDispatcher::close(const bool notify) {
//        if (OfflineReason::ONLINE == mofflineReason) {// not retrive reason yet
    analysisOfflineReason();
//       }

    LOG_WARN("in close, offline reason:%d", mofflineReason);//buildErrorMessage(ret, errno, message).data()

    bool result = msocket.close();

    if (ConnectionStatus::OFFLINE != mstatus) {
        mstatus = ConnectionStatus::OFFLINE;
        mserverReady = 0;

        if (notify) {
        for (NetworkListener* pNetworkListener : mnetworkListeners){
                pNetworkListener->offline(mnetworkManager.mpactiveNetwork);
                //LoopSchedulerSingleton::instance().offline(mnetworkManager.mpactiveNetwork);
            }
        }
    }

    if (OfflineReason::HARDWARE_FAILURE == mofflineReason || !--mretryCount) {

        mretryCount = MAX_RECONNECT;
        mnetworkManager.reactiveNetwork(mpCurrentNetwork, mofflineReason);

    } else if (!notify){
        ::sleep(1);
    }


    return result;
}




